[TOC]

torch特性

待补充

torch.Tensor

expand方法

expand方法并不会开辟新的内存,只是在已有tensor上创建了新的view,修改原tensor,会影响到expand返回的变量。
举例:

1
2
3
4
5
6
7
8
9
a.unsqueeze_(1)
b = a.expand(-1,4)
b = 1 1 1 1
2 2 2 2
3 3 3 3
a[1] = 4
b = 1 1 1 1
4 4 4 4
3 3 3 3

repeat方法

和expand不同,该方法拷贝了tensor的数据。

1
2
3
4
5
x = torch.Tensor([1,2,3])
x.repeat(3,2)
x = 1 2 3 1 2 3
1 2 3 1 2 3
1 2 3 1 2 3

gather

同torch.gather()方法。其返回变量开辟了新的内存。
gather方法的输入为一个input tensor,一个操作轴dim,一个LongTensor类型的index。
首先index的维度要与input tensor相匹配,比如input的size为3×4,有2个维度,index也要通过view或者unsqueeze等方法调整到2个维度,且index要与返回的tensor大小相同。
其次,dim=0时,表示的是根据index从input中逐列挑选,以构成并返回一个与input行维度相同的行tensor;dim=1也以此类比。

resize_

将tensor大小调整为指定大小。当指定size大于当前tensor的size时,将底层存储调整到与指定size相同。如果指定size小于当前tensor的size时,保持底层存储不变,调整tensor的size。(也就是说并不会丢失数据)

scatter_

将一个tensor x按照index确定的索引写入本tensor中。

1
2
x = torch.rand(2,5)
torch.zeros(3,5).scatter_(0, torch.LongTensor([[0,1,2,0,0], [2,0,0,1,2]]),x)

index的size必须要跟x的size相对应。
第一个参数dim=0表示沿着zeros矩阵的第一个维度进行scatter,例如上例子中index矩阵的第一行[0,1,2,0,0]分别代表x的第一行中各项在新矩阵的第一个维度中的下标。

torch.nn

torch.nn.Paramter

Parameters类是Variable的子类,但是它有一个非常独特的属性:当它被分配为Module的属性时,它会自动加到Module的参数列表中(即出现在parameters()迭代器中)。当我们需要在模型中缓存一些变量时(例如RNN的最后一层hidden state输出),我们就可以使用Variable而非Parameters,这样就可以避免将这些缓存作为模型中的参数变量。
除此之外,parameters不能够被设置为volatile,并且默认requires_grad=True。而Variable默认requires_grad=False。

torch.nn.Module

Module是所有神经网络类的基类。构建一个新的神经网络模型,只需要定义一个继承Module的新类,并在init方法中定义网络结构,再重载一个forward()函数。

1
2
3
4
5
6
7
8
9
10
11
import torch.nn as nn
import torch.nn.functional as F
class Model(nn.Module):
def __init__(self):
super(Model, self).__init__()
self.fc1 = nn.Linear(2,2)
self.fc2 = nn.Linear(2,2)
def forward(self, x):
x = F.relu(self.fc1(x))
x = F.relu(self.fc2(x))
return x

torch.nn.Module.register_parameter(name, param)

利用上面那种形式定义的网络,会自动把所有子模块的参数加入到模型的_parameters字典中。该方法则实现手动向module中加入一个parameter。

torch.nn.Module.register_buffer(name, tensor)

这个方法通常用来注册创建一个长期有效但不应属于模型参数的变量。如果变量名没被占用,则将tensor放入_buffers字典中。

1
self.register_buffer("running_mean", torch.zeros(num_features))

torch.nn.Module.add_module(name, module)

将一个子模块加入到当前模型。如果名字未被占用,则将该子模块加入_modules字典中。

torch.nn.Module.apply(fn)

对模型中所有的子模块应用fn方法。通常用来进行初始化。

hook in Module

为一个module注册一个hook函数,在计算梯度时自动触发。在不用修改主体代码的前提下,能够实现一些额外的功能,相当于是把它挂在了主体代码上,所以称为“钩子”。在pytorch中,会默认释放掉中间变量以减少对内存的压力。因此在训练一个网络时,想要提取中间层的参数、特征图的时候,就必须要利用到hook。
先贴一下Module的call方法实现的源码,后面会解释。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
def __call__(self, *input, **kwargs):
for hook in self._forward_pre_hooks.values():
hook(self, input)
if torch.jit._tracing:
result = self._slow_forward(*input, **kwargs)
else:
result = self.forward(*input, **kwargs)
for hook in self._forward_hooks.values():
hook_result = hook(self, input, result)
if hook_result is not None:
raise RuntimeError(
"forward hooks should never return any values, but '{}'"
"didn't return None".format(hook))
if len(self._backward_hooks) > 0:
var = result
while not isinstance(var, Variable):
if isinstance(var, dict):
var = next((v for v in var.values() if isinstance(v, Variable)))
else:
var = var[0]
grad_fn = var.grad_fn
if grad_fn is not None:
for hook in self._backward_hooks.values():
wrapper = functools.partial(hook, self)
functools.update_wrapper(wrapper, hook)
grad_fn.register_hook(wrapper)
return result

torch.nn.Module.register_forward_pre_hook(hook)

由此方法注册的hook会加入到_forward_pre_hooks字典中。由Module的源码中call函数可以看到,此处定义的hook函数会在forward之前执行,且没有返回值。

torch.nn.Module.register_forward_hook(hook)

由此方法注册的hook会加入到_forwardhooks字典中,在forward计算后执行。此处的hook函数返回值必须为None,不能修改输入项。因此通常只是用来作为中间结果的查看器。

torch.nn.Module.register_backward_hook(hook)

由此方法注册的hook会加入_backward_hooks中。执行时通过注册为Variable的hook函数来实现。这里的hook可以通过返回新的梯度来取代之前的梯度。
此处的hook函数应该定义为如下形式:

1
hook(module, grad_input,grad_output) -> Variable or None

torch.nn.Module.state_dict

返回一个包含_parameters、_buffers、各个modules的statedict的OrderedDict()

load_state_dict

读入一个OrderDict对象

torch.nn.Sequential

一个序列容器,其中的modules将按照加入的顺序执行。该容器有两种初始化方式:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
model = nn.Sequential(
nn.Conv2d(1,20,5),
nn.ReLU(),
nn.Conv2d(20,64,5),
nn.ReLU()
)
# or using OrderedDict
model = nn.Sequential(
OrderedDict([
('conv1', nn.Conv2d(1,20,5)),
('relu1', nn.ReLU()),
('conv2', nn.Conv2d(20,64,5)),
('relu2', nn.ReLU())
])
)

torch.nn.ModuleList

ModuleList将子module保存在一个列表里,可以像普通的list对象一样,通过下标的方式索引。同样地,ModuleList类也具有类似于list的append、extend方法。

1
2
3
4
5
6
7
8
class Model(nn.Module):
def __init__(self):
super(Model, self).__init__()
self.linears = nn.ModuleList([nn.Linear(10,10) for i in range(10)])
def forward(self,x):
for i,l in enumerate(self.linears):
x = self.linear[i // 2](x) + l(x)
return x

torch.nn.Conv1d

对于一个输入信号进行一维卷积操作。主要参数如下:

  • in_channels:输入通道数
  • out_channels:输出通道数
  • kernel_size:核大小
  • stride=1:步长
  • padding=0:填充(由于卷积核和输入信号的大小不一定匹配,因此可能会损失一些边界信息,所以可以采用padding进行填充)
  • dilation=1:控制卷积核点之间的间距可视化
  • groups=1:如果groups不为1,则将输入的通道分为groups组,对应专门的卷积核。当groups=in_channels,out_channels=Kin_channels*时,就是所谓的depthwise convolution,即对于每个通道单独做卷积处理。
  • bias=True: 偏差项
    输入、输出信号的形式:
    Input: $(N, C_{in}, L_{in})$
    Output: $(N, C_{out}, L_{out})$,这里
    $$L_{out} = floor((L_{in}+2*padding-dilation*(kernel\_size-1)-1)/stride+1)$$
    计算公式为:
    $$out(N_i, C_{out_j})=bias(C_{out_j})+\sum_{k=0}^{C_{in}-1}weight(C_{out_j},k)*input(N_i, k)$$

torch.nn.Conv2d

参数基本与Conv1d相同。但是由于输入输出都是二维信号,所以在kernelsize、stride、dilation等变量上可以采用不对等的形式。如下面代码所示。
输入、输出信号的形式:
Input: $(N, C_{in}, H_{in}, W_{in})$
Output: $(N, C_{out}, H_{out}, W_{out})$
$$H_{out} = floor((H_{in}+2*padding[0]-dilation[0]*(kernel\size[0]-1)-1)/stride[0]+1)$$
$$W
{out} = floor((W_{in}+2*padding[1]-dilation[1]*(kernel\_size[1]-1)-1)/stride[1]+1)$$

1
2
3
4
## square kernels and equal stride
m = nn.Conv2d(in_channels=16, out_channels=33, kernel_size=3, stride=2)
## non-square kernels and unequal stride and with padding and dilation
m = nn.Conv2d(in_channels=16, out_channels=33, kernel_size=(3,5), stride=(2,1), padding=(4,2), dilation=(3,1))

torch.nn.ConvTranspose2d

这个module可以视作Conv2d关于input的梯度。它可以被看做解卷积(Deconvolution)操作,但并不是真正的解卷积操作。

1
2
3
4
5
6
input = Variable(torch.randn(1,16,12,12))
downsample = nn.Conv2d(16,16,3,stride=2, padding=1)
upsample = nn.ConvTranspose2d(16,16,3,stride=2,padding=1)
h = downsample(input)
output = upsample(h, output_size=input.size())
## 计算重构损失

torch.nn.MaxPool2d

主要参数

  • kernel_size
  • stride=1
  • padding=0
  • dilation=1
  • return_indices:如果设为True,则返回outputs对应的最大点的坐标(unpooling操作能用到)
  • ceil_mode:如果设置为True,使用ceil代替floor来计算输出的shape

torch.nn.MaxUnpool2d

MaxPooling是无法完全可逆的,因为损失了一些非最大值的信息。
MaxUnpool接收MaxPool返回的最大值坐标,完成这部分信息的恢复,其他的非最大值处都被设置为0。
由于MaxPooling可以将多种输入大小映射到同样的输出大小,因此,反推结果可能就不唯一。为了解决这一点,可以在调用时,加入output_size参数指定。

1
2
3
4
5
6
7
8
9
pool = nn.MaxPool2d(2, stride=2, return_indices=True)
unpool = nn.MaxUnpool2d(2,stride=2)
input = Variable(torch.Tensor([[[[1,2,3,4],
[5,6,7,8],
[9,10,11,12],
[13,14,15,16]]]]))
output, indices = pool(intput)
unpool(output, indices)
unpool(output, indices, output_size=torch.Size([1,1,5,5]))

torch.nn.FractionalMaxPool2d

不同于传统pooling基于矩形关注区域操作,fractionalmaxpooling引入了更多随机性,它的stride是由目标输出大小确定的随机步长。

torch.nn.LPPool2d

幂平均池化。
$$f(x)=pow(sum(X,p),1/p)$$

torch.nn.AdaptiveMaxPool2d

自适应最大值池化。即指定输出的output_size,算法自动设置各项参数。如果为None则保持跟输入相同的大小。

torch.nn.Softmax2d

输入数据$(N,C,H,W)$,输出同大小。对于每个通道求softmax。

torch.nn.BatchNorm2d

$$y = \frac{x-mean[x]}{\sqrt{Var[x]+\epsilon}}*gamma+beta$$

1
2
3
4
m = nn.BatchNorm2d(100)
m = nn.BatchNorm2d(100, affine=False)
input = Variable(torch.randn(20,100,35,45))
output = m(input)

torch.nn.RNN

Multi-layer Elman RNN with tanh or ReLU
$$h_t = activation(w_{ih} x_t + b_{ih} + w_{hh} h_{(t-1)} + b_{hh})$$
其中activation可以是tanh或relu。这是一个最简单的RNN形式,每一时刻的$h_t$都由当前时刻的输入$w_{ih} x_t + b_{ih}$和上一时刻的状态$w_{hh} h_{(t-1)} + b_{hh}$共同决定。

  • input:
    • input(seq_len, batch, input_size)
    • h_0(num_layers*num_directions, batch, hidden_size):初始hiddenstate,若不设置则默认为0。
  • output:
    • output(seq_len, batch, hidden_size*num_directions)
    • h_n(num_layers*num_directions, batch, hidden_size)

torch.nn.LSTM

$$i_t = sigmoid(W_{ii}x_t+b_{ii}+W_{hi}h_{(t-1)}+b_{hi})$$
$$f_t = sigmoid(W_{if}x_t+b_{if}+W_{hf}h_{(t-1)}+b_{hf})$$
$$g_t = sigmoid(W_{ig}x_t+b_{ig}+W_{hc}h_{(t-1)}+b_{hg})$$
$$o_t = sigmoid(W_{io}x_t+b_{io}+W_{ho}h_{(t-1)}+b_{ho})$$
$$c_t = f_t*c_{(t-1)}+i_t*g_t$$
$$h_t = o_t*tanh(c_t)$$
此处的$h_t$是hidden state,$x_t$是前一层网络在$t$时刻的hidden state(或第一层的输入$input_t$),$i_t, f_t, g_t, o_t$分别是input gate、forget gate、cell gate和output gate。

torch.nn.GRU

$$r_t = sigmoid(W_{ir}x_t+b_{ir}+W_{hr}h_{(t-1)}+b_{hr})$$
$$z_t = sigmoid(W_{iz}x_t+b_{iz}+W_{hz}h_{(t-1)}+b_{hz})$$
$$n_t = tanh(W_{in}x_t+b_{in}+r_t*(W_{hn}h_{(t-1)}+b_{hn}))$$
$$h_t = (1-z_t)*n_t+z_t*h_{(t-1)}$$
此处的$h_t$是hidden state,$x_t$是前一层网络在$t$时刻的hidden state(或第一层的输入$input_t$),$r_t, z_t, n_t$分别是reset gate、input gate和new gate。

torch.nn.RNNCell

Elman RNN的核心部分。也就是说序列的部分需要自己手动完成,这样也增大了灵活性。
$$h’= activation(w_{ih} x + b_{ih} + w_{hh} h + b_{hh})$$

  • input:
    • input(batch, input_size)
    • hidden(batch, hidden_size):初始hidden state
  • output:
    • h’(batch, hidden_size)对于batch中所有个体在下个时刻的hidden state
      1
      2
      3
      4
      5
      6
      7
      rnn = nn.RNNCell(10,20)
      input = Variable(torch.randn(6,3,10))
      hx = Variable(torch.randn(3,20))
      output = []
      for i in range(input.size(0)):
      hx = rnn(input[i], hx)
      output.append(hx)

torch.nn.LSTMCell

  • input:
    • input(batch, input_size)
    • h_0(batch, hidden_size):初始hidden state
    • c_0(batch, hidden_size):初始cell memory
  • output:
    • h_1(batch, hidden_size):next hidden state for each element in the batch
    • c_1(batch, hidden_size):next cell state for each element in the batch
      1
      2
      3
      4
      5
      6
      7
      8
      lstm = nn.LSTMCell(10,20)
      input = Variable(torch.randn(6,3,10))
      hx = Variable(torch.randn(3,20))
      cx = Variable(torch.randn(3,20))
      output = []
      for i in range(input.size(0)):
      hx, cx = lstm(input[i], hx, cx)
      output.append(hx)

torch.nn.Embedding

一个速查表,存储着固定大小的字典。通常用来存储word embeddings,并使用下标来取回。输入是一个indices的列表,输出是对应的word embeddings。

  • Parameters
    • num_embeddings(int) - 输入的维数
    • embedding_dim(int) - 输出的向量维数
    • padding_idx(int, optional) - 设定输出为零的下标
    • max_norm(float, optional) - 设定输出向量的最大范数上界
    • norm_type(float, optional) - p-norm的p
    • scale_grad_by_freq(boolean, optional) - 根据字典中的单词频率来scale梯度
    • sparse(boolean, optional) - 如果为true,对于权重矩阵的偏导梯度将是一个sparse的tensor。
      目前只有一部分的optimizer支持sparse gradient:SGD、SparseAdam、Adagrad。
      1
      2
      3
      embedding = nn.Embedding(10,3)
      input = Variable(torch.Longtensor([[1,3,4,5],[3,4,2,7]]))
      embedding(input)

torch.nn.CrossEntropyLoss

这个类将LogSoftMax和NLLLoss结合在一起。经常用于多分类任务中。可选的参数weight能够给每个类分配权重,这在训练集不均衡时非常有用。
input需要是一个2D的Tensor,(minibatch, C)。
loss的形式为:
$$loss(x, class) = -log\frac{(exp(x[class])}{\sum_j exp(x[j]))}$$
$$loss(x, class) = -x[class] + log(\sum_j exp(x[j]))$$
weight参数被指定时:
$$loss(x, class) = weight[class] * (-x[class] + log(\sum_j exp(x[j])))$$

  • Parameters
    • weight(Tensor, optional)维度为C的Tensor
    • size_average(bool, optional)默认为true,即会对minibatch求avg loss;反之求sum loss
    • ignore_index(int, optional)设置一个target value,在计算梯度时将会忽略该类
    • reduce(bool, optional)默认为true,将每个minibatch的loss合并到一起(avg or sum);如果为false则忽略size_average,返回batch中每个element的loss。

torch.nn.NLLLoss

NLLLoss层前加上一个LogSoftmax层就等价于CrossEntropyLoss层。

torch.nn.PixelShuffle

利用多通道信息实现空间超分辨操作。

  • Input: (N, C*upscale_factor^2, H, W)
  • Output: (N, C, Hupscale_factor, Wupscale_factor)
    1
    2
    3
    4
    ps = nn.PixelShuffle(3)
    input = Variable(torch.Tensor(1,9,4,4))
    output = ps(input)
    output.size() # 1,1,12,12

torch.nn.DataParallel

该容器通过将输入数据划分到不同的设备(一般指GPU)上,来实现给定module的并行化应用。在每一次forward计算时,module被复制到每个设备上,并负责处理部分input。反向计算时,每个复制品的梯度被求和到一起。
batch size的大小要大于GPU的数量,最好要能够整除GPU的数量,以保证每个GPU处理相同数量的样本。
需要注意的是,module中定义的forward和backward hooks和他的submodules将不会被调用,除非hooks在forward()中被初始化。

torch.nn.DistributedDataParallel

该容器同样将输入数据划分到不同的设备上来实现并行。不同于DataParallel,该容器将module复制到每个machine和每个device,每个复制品处理一部分输入。在反向计算时,来自每个node的梯度被平均化。
Warnings见pytorch doc。

torch.nn.utils.clip_grad_norm

限制参数的最大范数值。

  • Parameters
    • parameters
    • max_norm
    • norm_type
  • Returns
    • 所有参数的p-norm值

torch.optim

如何使用optimizer

在定义optimizer时,需要指定被优化的参数。除此之外,还可以指定其他的优化参数,如学习率、weight decay等。如果需要将待优化参数放入GPU,则需要在定义optimizer之前。
Example

1
2
optimizer = optim.SGD(model.parameters(), lr=0.01, momentum=0.9)
optimizer = optim.Adam([var1, var2], lr=0.001)

Optimizer也支持对于每个优化对象指定单独的优化参数。传递可迭代的dicts,每一个dict都要定义一个单独的参数组,也要包含“params”关键字,除此之外,也可单独给每个dict指定优化参数。

1
2
3
4
5
6
7
optim.SGD([
{'params': model.base.parameters()},
{'params': model.classifier.parameters(), 'lr':1e-3}
],
lr = 1e-2,
momentum = 0.9
)

torch.optim.Optimizer

主要方法:
add_param_group:
往该optimizer中添加一个新的param group。
当finetune一个预训练的网络时非常有用,可以在训练过程中把一开始冻结的参数也加入训练。
load_state_dict
加载state。
state_dict
将optimizer的状态以字典的形式返回。
step
执行一次参数更新操作
zero_grad
清除所有待优化参数的gradient

如何调整Learning Rate

torch.optim.lr_scheduler提供了几种基于epoch数调整学习率的方法。